package cn.seaboot.flake.call;

import cn.seaboot.commons.lang.Warning;
import cn.seaboot.commons.reflect.FieldAccess;
import cn.seaboot.flake.mapping.ParameterMap;
import cn.seaboot.flake.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.CallableStatementCallback;

import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * 基于 {@link ParameterMap} 设计的 CallableStatementCallback，用于调用存储过程
 *
 * @author Mr.css
 * @version 2022-01-28 11:07
 */
public class FlackCallableStatementCallback implements CallableStatementCallback<Integer> {

    /**
     * 参数集配置
     */
    private final ParameterMap parameterMap;

    /**
     * 返回的结果，存储结果的 Out 结果会被放到这个对象中，兼容 Map 和 Object 两种类型
     */
    private final Object result;

    public FlackCallableStatementCallback(ParameterMap params, Object result) {
        this.parameterMap = params;
        this.result = result;
    }

    /**
     * 这里默认将存储过程当作 UPDATE 执行，最终返回受影响行数，返回值最终会被填写到参数中
     *
     * @param cs cs
     * @return effect's row
     * @throws SQLException        -
     * @throws DataAccessException -
     */
    @Override
    public Integer doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
        List<ParameterMapping> parameterMappings = parameterMap.getParameterMappings();
        // 注意下一行代码，并不是execute
        int ret = cs.executeUpdate();
        if (this.result instanceof Map) {
            this.processMap(parameterMappings, cs);
        } else {
            this.processObject(parameterMappings, cs);
        }
        return ret;
    }

    /**
     * 将结果查询成 Map
     *
     * @param mappings 参数配置
     * @param cs       -
     * @throws SQLException -
     */
    @SuppressWarnings(Warning.UNCHECKED)
    private void processMap(List<ParameterMapping> mappings, CallableStatement cs) throws SQLException {
        Map<String, Object> ret = (Map<String, Object>) result;
        for (int i = 0, len = mappings.size(); i < len; i++) {
            ParameterMapping mapping = mappings.get(i);
            if (mapping.getMode() != ParameterMode.IN) {
                ret.put(mapping.getProperty(), cs.getObject(i + 1));
            }
        }
    }

    /**
     * 将结果查询成 Object
     *
     * @param mappings 参数配置
     * @param cs       -
     * @throws SQLException -
     */
    private void processObject(List<ParameterMapping> mappings, CallableStatement cs) throws SQLException {
        FieldAccess fieldAccess = FieldAccess.create(result.getClass());
        for (int i = 0, len = mappings.size(); i < len; i++) {
            ParameterMapping mapping = mappings.get(i);
            if (mapping.getMode() != ParameterMode.IN) {
                fieldAccess.setValue(result, mapping.getProperty(), cs.getObject(i + 1));
            }
        }
    }
}
